/*
** SpritCtl.c
** Sprite control used to draw a graphic buffer using a specified Atari graphic mode
*/

#include <windows.h>
#include <windowsx.h>
#include <memory.h>
#include "spritctl.h"
#include "utils.h"

static void SpriteRefresh(HWND hWnd);

/*
** structure where buffer size and graphic mode characteristics are saved.
*/
typedef struct SpriteStruct
               {
               BYTE *pBuf;    /* graphic buffer address */
               WORD wSize;    /* size (in bytes) of the buffer to display */
               WORD wIndex;   /* index (in bytes) of current position in the buffer */
               WORD wMode;    /* Antic graphic mode number - 8 */
               WORD wNbBytes; /* number of bytes per line selected by user */
               WORD wEnd;     /* last byte of selection (SPRITE_NO_SELECTION = no selection) */
               } SpriteStruct;

typedef struct LOGPALETTE256
               {
               WORD           palVersion;
               WORD           palNumEntries;
               PALETTEENTRY   palPalEntry[256];
               } LOGPALETTE256;

/*
** characteristics of the different Antic graphic modes.
*/
static WORD wSpriteNbRows[]             = {  40,  80,  80, 160, 160, 160, 160, 320 };
static WORD wSpriteNbLines[]            = {  24,  48,  48,  96, 192,  96, 192, 192 };
static WORD wSpriteNbColors[]           = {   4,   2,   4,   2,   2,   4,   4,   1 };
static WORD wSpriteNbBytesPerLine[]     = {  10,  10,  20,  20,  20,  40,  40,  40 };
static WORD wSpriteNbPixelsPerByte[]    = {   4,   8,   4,   8,   8,   4,   4,   8 };
static WORD wSpritePixelWidth[]         = {   8,   4,   4,   2,   2,   2,   2,   1 };
static WORD wSpritePixelHeight[]        = {   8,   4,   4,   2,   1,   2,   1,   1 };

/*
** colors used to draw pixels.
*/
static COLORREF dwSpriteColor[]         = { RGB(0, 0, 0), RGB(255, 0, 0), RGB(0, 0, 255), RGB(255, 255, 255) };
static BYTE cSpritePalIndex[]           = { 0, 1, 2, 3 };

/*
** bitmap and palette used to draw buffer.
*/
static PDIB lpSpriteDib                 = 0;
static HPALETTE hSpritePal              = 0;
static LOGPALETTE256 sSpritePal;
static UINT uSpriteDIBUsage             = DIB_PAL_COLORS;

/*
** macros to get pixel bits from a byte.
*/
#define SPRITE_GET_PIXEL_1(cPixel, cByte) \
     { WORD wPixelNumber; \
     wPixelNumber = 7 - wPixel; \
     cPixel = cByte & cMask8[wPixelNumber]; \
     if (wPixelNumber) \
          cPixel >>= wPixelNumber; \
     cPixel = cSpritePalIndex[cPixel ? 3 : 0]; \
     }

#define SPRITE_GET_PIXEL_2(cPixel, cByte) \
     { WORD wPixelNumber; \
     wPixelNumber = 3 - wPixel; \
     cPixel = cByte & cMask4[wPixelNumber]; \
     if (wPixelNumber) \
          cPixel >>= (wPixelNumber << 1); \
     cPixel = cSpritePalIndex[cPixel]; \
     }


/*
** set size of buffer to display.
** wSize is the number of bytes of the buffer.
** This function does not redraw control.
*/
static void SpriteSetSize(HWND hWnd, WORD wSize)
{
     SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wSize), wSize);
     SetScrollRange(hWnd, SB_VERT, 0, wSize, FALSE);
}

/*
** WM_SPRITE_SET_BUFFER message
** set buffer size and pointer
** This function does not redraw control.
*/
static void SpriteSetBuffer(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     SetWindowLong(hWnd, FIELDOFFSET(SpriteStruct, pBuf), lParam);
     SpriteSetSize(hWnd, wParam);
}

/*
** WM_SPRITE_SET_NBBYTES message
** set number of bytes per lines.
** if bRefresh is TRUE, control is redrawn.
*/
static void SpriteSetNbBytes(HWND hWnd, WORD wNbBytes, BOOL bRefresh)
{
     if (wNbBytes <= 40)
          {
          SetScrollPos(hWnd, SB_HORZ, wNbBytes, TRUE);
          SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes), wNbBytes);
          if (bRefresh)
               SpriteRefresh(hWnd);
          }
}

/*
** WM_SPRITE_SET_MODE message
** set graphic mode and its characteristics.
** wMode is an Antic graphic mode (8 to 15).
** if bRefresh is TRUE, control is redrawn.
*/
static void SpriteSetMode(HWND hWnd, WORD wMode, BOOL bRefresh)
{
WORD wNbBytes;

     if ((wMode >= 8) && (wMode <= 15))
          {
          wMode -= 8;
          SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode), wMode);
          SetScrollRange(hWnd, SB_HORZ, 1, wSpriteNbBytesPerLine[wMode], FALSE);
          wNbBytes = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes));
          if (wNbBytes > wSpriteNbBytesPerLine[wMode])
               {
               SpriteSetNbBytes(hWnd, wSpriteNbBytesPerLine[wMode], FALSE);
               SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, SPRITE_NBBYTES_CHANGED));
               }
          else SpriteSetNbBytes(hWnd, wNbBytes, FALSE);
          if (bRefresh)
               SpriteRefresh(hWnd);
          }
}

/*
** WM_SPRITE_SET_INDEX message
** set first line to display
** if bRefresh is TRUE, control is redrawn.
*/
static void SpriteSetIndex(HWND hWnd, WORD wIndex, BOOL bRefresh)
{
     SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wIndex), wIndex);
     SetScrollPos(hWnd, SB_VERT, wIndex, TRUE);
     if (bRefresh)
          SpriteRefresh(hWnd);
}

/*
** WM_SPRITE_SET_SELECTION message
** set last byte selected.
** if bRefresh is TRUE, control is redrawn.
*/
static void SpriteSetSelection(HWND hWnd, WORD wEnd, BOOL bRefresh)
{
     SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd), wEnd);
     if (bRefresh)
          SpriteRefresh(hWnd);
}

/*
** WM_SPRITE_GET_INFO message
** get information on the wParam subject
*/
static long SpriteGetInfo(HWND hWnd, WPARAM wParam)
{
WORD wMode;
WORD wRet;

     wMode = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));
     switch (wParam)
          {
          case SPRITE_NBROWS:
               wRet = wSpriteNbRows[wMode];
               break;

          case SPRITE_NBLINES:
               wRet = wSpriteNbLines[wMode];
               break;

          case SPRITE_NBCOLORS:
               wRet = wSpriteNbColors[wMode];
               break;

          case SPRITE_NBBYTESPERLINE:
               wRet = wSpriteNbBytesPerLine[wMode];
               break;

          case SPRITE_NBPIXELSPERBYTE:
               wRet = wSpriteNbPixelsPerByte[wMode];
               break;

          case SPRITE_PIXELWIDTH:
               wRet = wSpritePixelWidth[wMode];
               break;

          case SPRITE_PIXELHEIGHT:
               wRet = wSpritePixelHeight[wMode];
               break;

          default:
               wRet = 0;
               break;
          }
     return (long) (DWORD) wRet;
}

/*
** WM_CREATE message
** Initialization of the window private structure in the extra bytes.
** The default setting is no buffer (0 byte long) and Antic graphic mode 13.
*/
static long SpriteCreate(HWND hWnd)
{
HDC hDC;
int i;

     /*
     ** prepare bitmap to display buffer
     */
     if ((hDC = GetDC(hWnd)) == 0)
          return -1;

     if (lpSpriteDib = DibNew(8, 320, 192))
          {
          sSpritePal.palNumEntries = 256;
          sSpritePal.palVersion = 0x300;
          GetSystemPaletteEntries(hDC, 0, 256, sSpritePal.palPalEntry);
          hSpritePal = CreatePalette((LOGPALETTE *) &sSpritePal);
          for (i = 0; i < sizeof(cSpritePalIndex); i++)
               cSpritePalIndex[i] = GetNearestPaletteIndex(hSpritePal, dwSpriteColor[i]);
          if (DibBitCount(lpSpriteDib) != 4)
               {
               DibMapToPalette(lpSpriteDib, hSpritePal);
               uSpriteDIBUsage = DIB_PAL_COLORS;
               DibSetUsage(lpSpriteDib, hSpritePal, uSpriteDIBUsage);
               }
          else uSpriteDIBUsage = DIB_RGB_COLORS;
          }
     ReleaseDC(hWnd, hDC);

     /*
     ** set default values
     */
     SpriteSetBuffer(hWnd, 0, NULL);
     SpriteSetMode(hWnd, 13, FALSE);
     SpriteSetIndex(hWnd, 0, FALSE);
     SpriteSetNbBytes(hWnd, 1, FALSE);
     SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd), SPRITE_NO_SELECTION);
     return (lpSpriteDib ? 0 : -1);
}

static long SpriteDestroy(HWND hWnd)
{
     if (lpSpriteDib)
          DibFree(lpSpriteDib);
     if (hSpritePal)
          DeleteObject(hSpritePal);
     return NULL;
}

static void SpritePaintAll(HWND hWnd, HDC hDC)
{
WORD wMode;
WORD wSize, wIndex;
WORD wRow, wLine;
WORD wPixelWidth, wPixelHeight;
WORD wNbBytes;
WORD wNbPixelsPerByte;
WORD wNbBytesPerLine;
WORD wEnd;
WORD w, wPixel;
WORD wMaxRow;
WORD wRowByte;
BYTE *lpBuffer;
BYTE *lpScreen;
BYTE cPixel;
BYTE cByte;
BYTE cInvert;
HPALETTE hOldPal;
static BYTE cMask4[] = { 0x03, 0x0C, 0x30, 0xC0 };
static BYTE cMask8[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };

     /*
     ** get graphic characteristics from window extra bytes.
     */
     wSize = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wSize));
     wNbBytes = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes));
     wIndex = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wIndex));
     lpBuffer = (BYTE *) ((DWORD) GetWindowLong(hWnd, FIELDOFFSET(SpriteStruct, pBuf)) + wIndex);
     wEnd = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd));
     wMode = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));
     wNbBytesPerLine = wSpriteNbBytesPerLine[wMode];
     wNbPixelsPerByte = wSpriteNbPixelsPerByte[wMode];
     wPixelWidth = wSpritePixelWidth[wMode];
     wPixelHeight = wSpritePixelHeight[wMode];

     /*
     ** draw graphics.
     */
     lpScreen = ((BYTE *) DibPtr(lpSpriteDib)) + ((DWORD) 320 * (192 - wPixelHeight));
     wRowByte = wPixelWidth * wNbPixelsPerByte;
     for (wLine = 0; wLine < 192; wLine += wPixelHeight)
          {

          /*
          ** stop at the end of dump buffer
          */
          if (wIndex >= wSize)
               break;

          /*
          ** Is this line selected ?
          */
          if ((wEnd != SPRITE_NO_SELECTION) && (wIndex <= wEnd))
               cInvert = 0xFF;
          else cInvert = 0;

          /*
          ** draw the line each byte at a time.
          */
          wMaxRow = min(wNbBytes, wSize - wIndex);
          for (wRow = 0; wRow < wMaxRow; wRow++)
               {

               /*
               ** get byte and invert it if there is a selection
               */
               cByte = (*lpBuffer++) ^ cInvert;
               for (wPixel = 0; wPixel < wNbPixelsPerByte; wPixel++)
                    switch (wMode)
                         {
                         case 0:
                              SPRITE_GET_PIXEL_2(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   {
                                   *(lpScreen + 960) = *(lpScreen + 640) = *(lpScreen + 320) =
                                        *(lpScreen + 2240) = *(lpScreen + 1920) = *(lpScreen + 1600) =
                                        *(lpScreen + 1280) = cPixel;
                                   *lpScreen++ = cPixel;
                                   }
                              break;

                         case 1:
                              SPRITE_GET_PIXEL_1(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   {
                                   *(lpScreen + 960) = *(lpScreen + 640) = *(lpScreen + 320) = cPixel;
                                   *lpScreen++ = cPixel;
                                   }
                              break;

                         case 2:
                              SPRITE_GET_PIXEL_2(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   {
                                   *(lpScreen + 960) = *(lpScreen + 640) = *(lpScreen + 320) = cPixel;
                                   *lpScreen++ = cPixel;
                                   }
                              break;

                         case 3:
                              SPRITE_GET_PIXEL_1(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   {
                                   *(lpScreen + 320) = cPixel;
                                   *lpScreen++ = cPixel;
                                   }
                              break;

                         case 4:
                              SPRITE_GET_PIXEL_1(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   *lpScreen++ = cPixel;
                              break;

                         case 5:
                              SPRITE_GET_PIXEL_2(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   {
                                   *(lpScreen + 320) = cPixel;
                                   *lpScreen++ = cPixel;
                                   }
                              break;

                         case 6:
                              SPRITE_GET_PIXEL_2(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   *lpScreen++ = cPixel;
                              break;

                         case 7:
                              SPRITE_GET_PIXEL_1(cPixel, cByte);
                              for (w = 0; w < wPixelWidth; w++)
                                   *lpScreen++ = cPixel;
                              break;
                         }
               }

          /*
          ** fill the rest of the line (take selection into account)
          */
          if (wRow < wNbBytesPerLine)
               {
               wRow = (wNbBytesPerLine - wRow) * wRowByte;
               switch (wPixelHeight)
                    {
                    case 8:
                         memset(lpScreen + 2240, cInvert, wRow);
                         memset(lpScreen + 1920, cInvert, wRow);
                         memset(lpScreen + 1600, cInvert, wRow);
                         memset(lpScreen + 1280, cInvert, wRow);
                    case 4:
                         memset(lpScreen + 960, cInvert, wRow);
                         memset(lpScreen + 640, cInvert, wRow);
                    case 2:
                         memset(lpScreen + 320, cInvert, wRow);
                    case 1:
                         memset(lpScreen, cInvert, wRow);
                         break;
                    }
               lpScreen += wRow;
               }
          wIndex += wMaxRow;
          lpScreen -= 320 * (wPixelHeight + 1);
          }

     /*
     ** fill the rest of the screen (no selection)
     */
     for (lpScreen = DibPtr(lpSpriteDib); wLine < 192; wLine++, lpScreen += 320)
          memset(lpScreen, 0, 320);

     /*
     ** draw bitmap on screen.
     */
     hOldPal = SelectPalette(hDC, hSpritePal, FALSE);
     RealizePalette(hDC);
     StretchDIBits(hDC, 0, 0, 320, 192,
                   0, 0, DibWidth(lpSpriteDib), DibHeight(lpSpriteDib), DibPtr(lpSpriteDib), DibInfo(lpSpriteDib),
                   uSpriteDIBUsage, SRCCOPY);
     SelectPalette(hDC, hOldPal, FALSE);
}

/*
** WM_PAINT message
** redraw the graphic buffer
*/
static long SpritePaint(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;

     hDC = BeginPaint(hWnd, &ps);
     SpritePaintAll(hWnd, hDC);
     EndPaint(hWnd, &ps);
     return NULL;
}

/*
** refresh window
*/
static void SpriteRefresh(HWND hWnd)
{
HDC hDC;

     hDC = GetDC(hWnd);
     if (hDC)
          {
          SpritePaintAll(hWnd, hDC);
          ReleaseDC(hWnd, hDC);
          }
}

/*
** WM_HSCROLL message
*/
static long SpriteHScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
WORD wOldNbBytes;
WORD wNbBytes;
WORD wMaxNbBytes;
WORD wMode;

     wMode = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));
     wOldNbBytes = wNbBytes = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes));
     wMaxNbBytes = wSpriteNbBytesPerLine[wMode];
     switch (wParam)
          {
          case SB_TOP:
               if (wNbBytes > 1)
                    wNbBytes = 1;
               break;

          case SB_BOTTOM:
               if (wNbBytes != wMaxNbBytes)
                    wNbBytes = wMaxNbBytes;
               break;

          case SB_PAGEUP:
               if (wNbBytes > 1)
                    wNbBytes--;
          case SB_LINEUP:
               if (wNbBytes > 1)
                    wNbBytes--;
               break;

          case SB_PAGEDOWN:
               if (wNbBytes != wMaxNbBytes)
                    wNbBytes++;
          case SB_LINEDOWN:
               if (wNbBytes != wMaxNbBytes)
                    wNbBytes++;
               break;

          case SB_THUMBPOSITION:
          case SB_THUMBTRACK:
               if ((wNbBytes != LOWORD(lParam)) && (LOWORD(lParam) <= wMaxNbBytes))
                    wNbBytes = LOWORD(lParam);
               break;
          }
     if (wOldNbBytes != wNbBytes)
          {
          SpriteSetNbBytes(hWnd, wNbBytes, TRUE);
          SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, SPRITE_NBBYTES_CHANGED));
          }
     return NULL;
}

/*
** WM_VSCROLL message
*/
static long SpriteVScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
WORD wOldIndex;
WORD wIndex;
WORD wMaxIndex;
WORD wMode;

     wMode = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));
     wOldIndex = wIndex = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wIndex));
     wMaxIndex = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wSize));
     switch (wParam)
          {
          case SB_TOP:
               if (wIndex)
                    wIndex = 0;
               break;

          case SB_BOTTOM:
               if (wIndex != wMaxIndex)
                    wIndex = wMaxIndex;
               break;

          case SB_LINEUP:
               if (wIndex)
                    wIndex--;
               break;

          case SB_LINEDOWN:
               if (wIndex != wMaxIndex)
                    wIndex++;
               break;

          case SB_PAGEUP:
               if (wIndex)
                    wIndex -= min(wIndex, wSpriteNbBytesPerLine[wMode]);
               break;

          case SB_PAGEDOWN:
               if (wIndex != wMaxIndex)
                    wIndex += min(wMaxIndex - wIndex, wSpriteNbBytesPerLine[wMode]);
               break;

          case SB_THUMBPOSITION:
          case SB_THUMBTRACK:
               if ((wIndex != LOWORD(lParam)) && (LOWORD(lParam) <= wMaxIndex))
                    wIndex = LOWORD(lParam);
               break;
          }
     if (wOldIndex != wIndex)
          {
          SpriteSetIndex(hWnd, wIndex, TRUE);
          SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, SPRITE_INDEX_CHANGED));
          }
     return NULL;
}

/*
** WM_LBUTTONDOWN message
*/
static long SpriteLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
WORD wMode;
WORD wNbBytes;
WORD wIndex;
WORD wPixelHeight;
WORD wOldEnd, wNewEnd;

     /*
     ** get buffer and selection from window extra bytes.
     */
     wOldEnd = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd));
     wNbBytes = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes));
     wIndex = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wIndex));
     wMode = GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));
     wPixelHeight = wSpritePixelHeight[wMode];

     /*
     ** get buffer offset from selection line.
     */
     wNewEnd = wIndex + ((HIWORD(lParam) / wPixelHeight) * wNbBytes) - 1;

     /*
     ** if selection has changed, save new selection, redraw control and notify parent window.
     */
     if (wOldEnd != wNewEnd)
          {
          SetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd), wNewEnd);
          SpriteRefresh(hWnd);
          SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, SPRITE_SELECTION_CHANGED));
          }
     return NULL;
}

/*
** WM_MOUSEMOVE message
*/
static long SpriteMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     if (wParam & MK_LBUTTON)
          return SpriteLButtonDown(hWnd, wParam, lParam);
     return NULL;
}

/*
** WM_LBUTTONUP message
*/
static long SpriteLButtonUp(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     return SpriteLButtonDown(hWnd, wParam, lParam);
}

/*
** Sprite window proc.
*/
long CALLBACK __export SpriteWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
          {
          case WM_SPRITE_SET_BUFFER:
               SpriteSetBuffer(hWnd, wParam, lParam);
               break;

          case WM_SPRITE_SET_MODE:
               SpriteSetMode(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_SPRITE_SET_INDEX:
               SpriteSetIndex(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_SPRITE_SET_NBBYTES:
               SpriteSetNbBytes(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_SPRITE_SET_SELECTION:
               SpriteSetSelection(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_SPRITE_GET_MODE:
               return GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wMode));

          case WM_SPRITE_GET_INDEX:
               return GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wIndex));

          case WM_SPRITE_GET_NBBYTES:
               return GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wNbBytes));

          case WM_SPRITE_GET_SELECTION:
               return GetWindowWord(hWnd, FIELDOFFSET(SpriteStruct, wEnd));

          case WM_SPRITE_GET_INFO:
               return SpriteGetInfo(hWnd, wParam);

          case WM_SPRITE_REFRESH:
               SpriteRefresh(hWnd);
               break;

          case WM_CREATE:
               return SpriteCreate(hWnd);

          case WM_DESTROY:
               return SpriteDestroy(hWnd);

          case WM_PAINT:
               return SpritePaint(hWnd);

          case WM_HSCROLL:
               return SpriteHScroll(hWnd, wParam, lParam);

          case WM_VSCROLL:
               return SpriteVScroll(hWnd, wParam, lParam);

          case WM_LBUTTONDOWN:
               return SpriteLButtonDown(hWnd, wParam, lParam);

          case WM_MOUSEMOVE:
               return SpriteMouseMove(hWnd, wParam, lParam);

          case WM_LBUTTONUP:
               return SpriteLButtonUp(hWnd, wParam, lParam);

          default:
               return DefWindowProc(hWnd, message, wParam, lParam);
          }
     return NULL;
}

BOOL CALLBACK __export SpriteInit(HINSTANCE hInst)
{
WNDCLASS wc;

     /*
     ** Fill in window class structure with parameters that describe the
     ** sprite control.
     */
     wc.style         = NULL;
     wc.lpfnWndProc   = SpriteWndProc;
     wc.cbClsExtra    = 0;
     wc.cbWndExtra    = sizeof(SpriteStruct);
     wc.hInstance     = hInst;
     wc.hIcon         = NULL;
     wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground = GetStockObject(BLACK_BRUSH);
     wc.lpszMenuName  = NULL;
     wc.lpszClassName = "SpriteCtrlClass";

     /*
     ** Register the control class.
     */
     return RegisterClass(&wc);
}

/*
** DLL entry point
*/
BOOL CALLBACK LibMain(HINSTANCE hinst, UINT wDS, UINT cbHeap, DWORD unused)
{
     return TRUE;
}

/*
** DLL exit point
*/
int FAR PASCAL _WEP(int unused)
{
     return TRUE;
}
